/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;

using Anculus.Core;

namespace Galaxium.Protocol.Msn
{
	public enum MsnP2PApplicationState { Waiting, Active, Complete, Cancelled, Error }
	
	public abstract class AbstractMsnP2PApplication : IMsnP2PApplication
	{
		public event EventHandler Complete;
		public event EventHandler Error;
		
		public event EventHandler Began;
		public event EventHandler LocallyCancelled;
		public event EventHandler RemotelyCancelled;
		
		MsnP2PSession _p2pSession;
		
		MsnAccount _local;
		MsnContact _remote;
		MsnSession _session;
		
		MsnP2PApplicationState _state = MsnP2PApplicationState.Waiting;
		
		static uint _dbgAppCount = 0;
		protected uint _dbgAppID = _dbgAppCount++;
		
		public virtual bool AutoAccept
		{
			get { return false; }
		}
		
		public MsnP2PApplicationState State
		{
			get { return _state; }
		}
		
		public MsnP2PSession P2PSession
		{
			get { return _p2pSession; }
			set
			{
				if (_p2pSession != null)
				{
					_p2pSession.Closing -= P2PSessionClosing;
					_p2pSession.Closed -= P2PSessionClosed;
					_p2pSession.Error -= P2PSessionError;
				}
				
				_p2pSession = value;
				
				if (_p2pSession != null)
				{
					_p2pSession.Closing += P2PSessionClosing;
					_p2pSession.Closed += P2PSessionClosed;
					_p2pSession.Error += P2PSessionError;
				}
			}
		}
		
		public virtual uint AppID
		{
			// Return the AppID MsnP2PUtility got from the MsnP2PApplicationAttribute
			get { return MsnP2PUtility.GetAppID (this); }
		}
		
		public MsnAccount Local
		{
			get { return _local; }
		}
		
		public MsnContact Remote
		{
			get { return _remote; }
		}
		
		public MsnSession Session
		{
			get { return _session; }
		}
		
		protected AbstractMsnP2PApplication (MsnAccount local, MsnContact remote)
		{
			_local = local;
			_remote = remote;
			_session = _local.Session;
			
			Log.Debug ("P2PApp {0} created ({1})", _dbgAppID, GetType().Name);
		}
		
		protected AbstractMsnP2PApplication (MsnP2PSession p2pSession)
			: this (p2pSession.Local, p2pSession.Remote)
		{
			P2PSession = p2pSession;
			
			Log.Debug ("P2PApp {0} associated with session {1}", _dbgAppID, _p2pSession.SessionID);
		}
		
		public virtual void Dispose ()
		{
			P2PSession = null;
			
			if ((_state != MsnP2PApplicationState.Cancelled) &&
			    (_state != MsnP2PApplicationState.Complete) &&
			    (_state != MsnP2PApplicationState.Error))
			{
				// Disposal in any non-complete state signals an error
				OnError ();
			}
			
			Log.Debug ("P2PApp {0} disposed", _dbgAppID);
		}
		
		public abstract bool ProcessMessage (IMsnP2PBridge bridge, P2PMessage msg);
		
		public virtual void Send (P2PMessage msg, AckHandler ackHandler)
		{
			msg.Header.SessionID = _p2pSession.SessionID;
			
			// Set the MessageID, but only if it's not already set
			if (msg.Header.MessageID == 0)
				msg.Header.MessageID = _p2pSession.NextID ();
			
			// If not an ack, set the footer to the application ID
			if ((msg.Header.Flags & P2PHeaderFlag.Ack) != P2PHeaderFlag.Ack)
				msg.Footer = MsnP2PUtility.GetAppID (this);
			
			_p2pSession.Send (msg, ackHandler);
		}
		
		public void Send (P2PMessage msg)
		{
			Send (msg, null);
		}
		
		public virtual void BridgeIsReady ()
		{
		}
		
		protected void OnComplete ()
		{
			Log.Debug ("P2PApp {0} complete", _dbgAppID);
			
			_state = MsnP2PApplicationState.Complete;
			
			if (this.Complete != null)
				this.Complete (this, EventArgs.Empty);
		}
		
		protected void OnError ()
		{
			Log.Debug ("P2PApp {0} error", _dbgAppID);
			
			_state = MsnP2PApplicationState.Error;
			
			if (this.Error != null)
				this.Error (this, EventArgs.Empty);
		}
		
		public virtual bool CheckInvite (SLPRequestMessage invite)
		{
			// If the invite isn't for us, it isn't valid
			return invite.To == invite.Session.Account;
		}
		
		public abstract string CreateInviteContext ();
		
		public virtual void Begin ()
		{
			OnBegan ();
		}
		
		public virtual void Accept ()
		{
			_p2pSession.Accept ();
		}
		
		public virtual void Decline ()
		{
			_p2pSession.Decline ();
		}
		
		public virtual void Cancel ()
		{
			OnLocallyCancelled ();
			_p2pSession.Close ();
		}
		
		internal void OnBegan ()
		{
			Log.Debug ("P2PApp {0} began", _dbgAppID);
			
			_state = MsnP2PApplicationState.Active;
			
			if (this.Began != null)
				this.Began (this, EventArgs.Empty);
		}
		
		internal void OnLocallyCancelled ()
		{
			Log.Debug ("P2PApp {0} cancelled locally", _dbgAppID);
			
			_state = MsnP2PApplicationState.Cancelled;
			
			if (this.LocallyCancelled != null)
				this.LocallyCancelled (this, EventArgs.Empty);
		}
		
		internal void OnRemotelyCancelled ()
		{
			Log.Debug ("P2PApp {0} cancelled remotely", _dbgAppID);
			
			_state = MsnP2PApplicationState.Cancelled;
			
			if (this.RemotelyCancelled != null)
				this.RemotelyCancelled (this, EventArgs.Empty);
		}
		
		void P2PSessionClosing (object sender, EntityEventArgs args)
		{
			if (args.Entity == Session.Account)
			{
				// Closing initiated locally
			}
			else
			{
				// Closing initiated remotely
				
				if (_state == MsnP2PApplicationState.Waiting || _state == MsnP2PApplicationState.Active)
					OnRemotelyCancelled ();
			}
		}
		
		void P2PSessionClosed (object sender, EntityEventArgs args)
		{
			if (args.Entity == Session.Account)
			{
				// Closed locally
				
				if (_state == MsnP2PApplicationState.Waiting || _state == MsnP2PApplicationState.Active)
					OnLocallyCancelled ();
			}
			else
			{
				// Closed remotely
				
				if (_state == MsnP2PApplicationState.Waiting || _state == MsnP2PApplicationState.Active)
					OnRemotelyCancelled ();
			}
		}
		
		void P2PSessionError (object sender, EventArgs args)
		{
			if (_state != MsnP2PApplicationState.Error)
				OnError ();
		}
	}
}
